home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / TPTUTR~1.ZIP / PASCAL11.TXT < prev    next >
Text File  |  1996-03-21  |  38KB  |  998 lines

  1.                         Turbo Pascal for DOS Tutorial
  2.                              by Glenn Grotzinger
  3.             Part 11 -- data representation; reading specs sheets
  4.                   copyright (c) 1995-96 by Glenn Grotzinger
  5.  
  6. Is there still any interest in this tutorial?  If so, tell me! :>
  7.  
  8. Here is the solution I got from part 10...No one tried it and sent it to me.
  9. Here's the UNIT.  Keep in mind that I said to JUST CREATE ONE.  You don't
  10. have to have the same functions that I had in there.  Just as long as it
  11. works....
  12.  
  13. {$O+}
  14. unit unit10;
  15.  
  16.   interface
  17.   type
  18.     strtype = array[1..3] of string[80];
  19.     {$I COMPHVN.INC}
  20.  
  21.     function numeric(str: string): boolean;
  22.     procedure writerecord(var outfile: file; strs: strtype);
  23.  
  24.   implementation
  25.  
  26.   function numeric(str: string): boolean;
  27.     var
  28.       num: boolean;
  29.       i: integer;
  30.  
  31.     begin
  32.       i := 1;
  33.       num := true;
  34.       while (num) and (i <= length(str)) do
  35.         begin
  36.           num := (str[i] in ['0'..'9','.',' ']);
  37.           inc(i);
  38.         end;
  39.       numeric := num;
  40.     end;
  41.  
  42.   procedure writerecord(var outfile: file; strs: strtype);
  43.     var
  44.       numerr: integer;
  45.       writerec: comphvndata;
  46.     begin
  47.       with writerec do
  48.         begin
  49.           {part 1}
  50.           datacode := copy(strs[1], 1, 7);
  51.           acct_classification := strs[1][8];
  52.           val(copy(strs[1], 10, 3), phone_area, numerr);
  53.           val(copy(strs[1], 13, 3), phone_prefix, numerr);
  54.           val(copy(strs[1], 16, 4), phone_exchange, numerr);
  55.           val(copy(strs[1], 20, 3), work_area, numerr);
  56.           val(copy(strs[1], 23, 3), work_prefix, numerr);
  57.           val(copy(strs[1], 26, 4), work_exchange, numerr);
  58.           val(copy(strs[1], 30, 3), other_area, numerr);
  59.           val(copy(strs[1], 33, 3), other_prefix, numerr);
  60.           val(copy(strs[1], 36, 4), other_exchange, numerr);
  61.           cnct1_lname := copy(strs[1], 40, 16);
  62.           cnct1_fname := copy(strs[1], 56, 11);
  63.           cnct1_minit := strs[1][67];
  64.           val(copy(strs[1], 68, 5), cnct1_pobox, numerr);
  65.           cnct1_sname := copy(strs[1], 73, 8);
  66.  
  67.           {part2}
  68.           accept_check := (strs[2][8] = 'Y');
  69.           cnct1_stype := copy(strs[2], 10, 4);
  70.           val(copy(strs[2], 14, 4), cnct1_apt, numerr);
  71.           cnct1_city := copy(strs[2], 18, 10);
  72.           cnct1_state := copy(strs[2], 28, 2);
  73.           val(copy(strs[2], 30, 9), cnct1_zip, numerr);
  74.           val(copy(strs[2], 39, 2), cnct1_birthm, numerr);
  75.           val(copy(strs[2], 41, 2), cnct1_birthd, numerr);
  76.           val(copy(strs[2], 43, 4), cnct1_birthy, numerr);
  77.           val(copy(strs[2], 47, 9), balnce_credt, numerr);
  78.           val(copy(strs[2], 56, 8), total_sold, numerr);
  79.           cnct1_emp_code := copy(strs[2], 64, 4);
  80.           val(copy(strs[2], 68, 3), total_sales, numerr);
  81.           emp_name := copy(strs[2], 71, 10);
  82.  
  83.           {part3}
  84.           accept_credt := (strs[3][8] = 'Y');
  85.           val(copy(strs[3], 10, 4), emp_stnum, numerr);
  86.           emp_sttype := copy(strs[3], 14, 4);
  87.           emp_city := copy(strs[3], 18, 10);
  88.           emp_state := copy(strs[3], 28, 2);
  89.           val(copy(strs[3], 39, 3), emp_area, numerr);
  90.           val(copy(strs[3], 42, 3), emp_prefix, numerr);
  91.           val(copy(strs[3], 45, 4), emp_exchange, numerr);
  92.           val(copy(strs[3], 49, 2), emp_yrs, numerr);
  93.           compu := (strs[3][51] = 'Y');
  94.           compu_type := copy(strs[3], 52, 9);
  95.           compu_mon := strs[3][61];
  96.           compu_cdr := (strs[3][62] = 'Y');
  97.           compu_cdt := strs[3][63];
  98.           val(copy(strs[3], 64, 2), compu_mem, numerr);
  99.           minor := (strs[3][66] = 'Y');
  100.         end;
  101.         blockwrite(outfile, writerec, sizeof(writerec));
  102.       end;
  103.  
  104.   end.
  105. And now here is the main program I got for part 10.  What I can see that is
  106. not readily explainable here (I used every method I could think of that
  107. falls into the rules I outlined for the thing.).  The two compiler directives
  108. you see below turn off stack checking and range checking respectively.
  109. With regards to data intensive applications such as sorting, large #'s of
  110. comparisons, and so forth, adding these speeds it up.
  111.  
  112. {$S-}
  113. {$R-}
  114. program part10; uses dos, unit10, overlay;
  115.  
  116.   {$O UNIT10.TPU}
  117.   var
  118.     strs: strtype;
  119.     outfile: file;
  120.     infile, errfile: text;
  121.     i: integer;
  122.     errwritten: boolean;
  123.  
  124.   procedure writeerror(errline, errtype: string);
  125.     var
  126.       temp1: string;
  127.     begin
  128.       temp1 := copy(errline, 1, 20);
  129.       writeln(errfile, temp1,'':10, errtype);
  130.       errwritten := true;
  131.     end;
  132.  
  133.   function checkstatus(strs: strtype): boolean;
  134.     var
  135.       check: boolean;
  136.       seqs: array[1..3] of char;
  137.       cnts: array[1..3] of byte;
  138.       i: byte;
  139.       errtype: string;
  140.     begin
  141.       check := true;
  142.       for i := 1 to 3 do
  143.         cnts[i] := 0;
  144.       for i := 1 to 3 do
  145.         begin
  146.           seqs[i] := strs[i][9];
  147.           case seqs[i] of
  148.             '1': inc(cnts[1]);
  149.             '2': inc(cnts[2]);
  150.             '3': inc(cnts[3]);
  151.           end;
  152.         end;
  153.       for i := 1 to 3 do
  154.         begin
  155.           if cnts[i] = 0 then
  156.             begin
  157.               errtype := 'Missing line #' + chr(i+48);
  158.               writeerror(strs[i], errtype);
  159.               check := false;
  160.             end;
  161.           if cnts[i] = 2 then
  162.             begin
  163.               errtype := 'Duplicate line #' + chr(i+48);
  164.               writeerror(strs[i], errtype);
  165.               check := false;
  166.             end;
  167.         end;
  168.       checkstatus := check;
  169.     end;
  170.  
  171.   procedure checkdatacodes(strs: strtype);
  172.     var
  173.       datacodes: array[1..3] of string[7];
  174.       check1: string[5];
  175.       check2: char;
  176.       error: string[20];
  177.     begin
  178.       for i := 1 to 3 do
  179.         datacodes[i] := copy(strs[i], 1, 7);
  180.       check1 := copy(strs[1], 40, 5);
  181.       check2 := strs[1][56];
  182.       for i := 1 to 3 do
  183.         begin
  184.           if datacodes[i][6] <> '*' then
  185.             writeerror(strs[i], 'Invalid datacode.');
  186.           if (check1 <> copy(datacodes[i], 1, 5)) or
  187.              (check2 <> datacodes[i][7]) then
  188.             writeerror(strs[i], 'Datacode does not agree with name.');
  189.         end;
  190.     end;
  191.  
  192.   procedure checknumeric(strs: strtype);
  193.     var
  194.       temp1, temp2: string;
  195.       int, numerr, numdiff: integer;
  196.       year, month, day, dayofweek: word;
  197.       empyrs, birthy, bmo, bday: word;
  198.       age: byte;
  199.       isminor: boolean;
  200.  
  201.     begin
  202.       if numeric(copy(strs[1], 10, 3)) = false then
  203.         writeerror(strs[1], 'Phone-area is not numeric.');
  204.       if numeric(copy(strs[1], 13, 3)) = false then
  205.         writeerror(strs[1], 'Phone-prefix is not numeric.');
  206.       if numeric(copy(strs[1], 16, 4)) = false then
  207.         writeerror(strs[1], 'Phone-exchange is not numeric.');
  208.       if numeric(copy(strs[1], 20, 3)) = false then
  209.         writeerror(strs[1], 'Work-area is not numeric.');
  210.       if numeric(copy(strs[1], 23, 3)) = false then
  211.         writeerror(strs[1], 'Work-prefix is not numeric.');
  212.       if numeric(copy(strs[1], 26, 4)) = false then
  213.         writeerror(strs[1], 'Work-exchange is not numeric.');
  214.       if numeric(copy(strs[1], 30, 3)) = false then
  215.         writeerror(strs[1], 'Other-area is not numeric.');
  216.       if numeric(copy(strs[1], 33, 3)) = false then
  217.         writeerror(strs[1], 'Other-prefix is not numeric.');
  218.       if numeric(copy(strs[1], 36, 4)) = false then
  219.         writeerror(strs[1], 'Other-exchange is not numeric.');
  220.       if numeric(copy(strs[1], 68, 5)) = false then
  221.         writeerror(strs[1], 'cnct1-pobox is not numeric.');
  222.       if numeric(copy(strs[3], 30, 9)) = false then
  223.         writeerror(strs[3], 'emp-zip is not numeric.');
  224.       if numeric(copy(strs[3], 39, 3)) = false then
  225.         writeerror(strs[3], 'emp-area is not numeric.');
  226.       if numeric(copy(strs[3], 42, 3)) = false then
  227.         writeerror(strs[3], 'emp-prefix is not numeric.');
  228.       if numeric(copy(strs[3], 45, 4)) = false then
  229.         writeerror(strs[3], 'emp-exchange is not numeric.');
  230.       temp2 := copy(strs[3], 49, 2);
  231.       if numeric(temp2) = false then
  232.         writeerror(strs[3], 'emp-yrs is not numeric.')
  233.       else
  234.         val(temp2, empyrs, numerr);
  235.       if numeric(copy(strs[2], 30, 9)) = false then
  236.         writeerror(strs[2], 'cnct1-zip is not numeric.');
  237.       temp2 := copy(strs[2], 39, 2);
  238.       if numeric(temp2) = false then
  239.         writeerror(strs[2], 'cnct1-birthm is not numeric.')
  240.       else
  241.         val(temp2, bmo, numerr);
  242.       temp2 := copy(strs[2], 41, 2);
  243.       if numeric(temp2) = false then
  244.         writeerror(strs[2], 'cnct1-birthd is not numeric.')
  245.       else
  246.         val(temp2, bday, numerr);
  247.  
  248.       temp1 := copy(strs[2], 43, 4);
  249.       if numeric(temp1) = false then
  250.         writeerror(strs[2], 'cnct1-birthy is not numeric.')
  251.       else
  252.         begin
  253.           val(temp1, int, numerr);
  254.           if (int < 1900) or (int > 1999) then
  255.             writeerror(strs[2], 'cnct1-birthy is not in this century.');
  256.         end;
  257.  
  258.       getdate(year, month, day, dayofweek);
  259.       val(temp1, birthy, numerr);
  260.       numdiff := year - birthy;
  261.       if numdiff < empyrs then
  262.         writeerror(strs[3], 'The emp-yrs doesn''t make sense.');
  263.  
  264.       age := year - birthy;
  265.       if month < bmo then dec(age);
  266.       if month = bmo then
  267.         if day < bday then
  268.           dec(age);
  269.       isminor := (age < 21);
  270.       if ((isminor = true) and (strs[3][66] = 'N')) or
  271.          ((isminor = false) and (strs[3][66] = 'Y')) then
  272.         writeerror(strs[3], 'Minor is set incorrectly.');
  273.  
  274.  
  275.       if numeric(copy(strs[2], 47, 9)) = false then
  276.         writeerror(strs[2], 'balance-credt is not numeric.');
  277.       if numeric(copy(strs[2], 56, 8)) = false then
  278.         writeerror(strs[2], 'total-sold is not numeric.');
  279.       if numeric(copy(strs[2], 68, 3)) = false then
  280.         writeerror(strs[2], 'total-sales is not numeric.');
  281.       if numeric(copy(strs[3], 64, 2)) = false then
  282.         writeerror(strs[3], 'compu-mem is not numeric.');
  283.     end;
  284.  
  285.  procedure checkprefix(strs: strtype);
  286.    begin
  287.      if strs[1][13] in ['0'..'1'] then
  288.        writeerror(strs[1], 'phone-prefix started with a 0 or 1.');
  289.      if strs[1][23] in ['0'..'1'] then
  290.        writeerror(strs[1], 'work-prefix started with a 0 or 1.');
  291.      if strs[1][33] in ['0'..'1'] then
  292.        writeerror(strs[1], 'other-prefix started with a 0 or 1.');
  293.      if strs[3][42] in ['0'..'1'] then
  294.        writeerror(strs[3], 'emp-prefix started with a 0 or 1.');
  295.    end;
  296.  
  297.  procedure checkacct(strs: strtype);
  298.    begin
  299.      if strs[1][8] in ['B','C','G','P','O'] then
  300.      else
  301.        writeerror(strs[1], 'acct-classification is invalid.');
  302.    end;
  303.  
  304.  procedure checkyn(strs: strtype);
  305.    begin
  306.      if strs[2][8] in ['Y', 'N'] then
  307.      else
  308.        writeerror(strs[2], 'accept-check is invalid.');
  309.      if strs[3][8] in ['Y', 'N'] then
  310.      else
  311.        writeerror(strs[3], 'accept-credt is invalid.');
  312.      if strs[3][51] in ['Y', 'N'] then
  313.      else
  314.        writeerror(strs[3], 'compu is invalid.');
  315.      if strs[3][62] in ['Y', 'N'] then
  316.      else
  317.        writeerror(strs[3], 'compu-cdr is invalid.');
  318.      if strs[3][66] in ['Y', 'N'] then
  319.      else
  320.        writeerror(strs[3], 'minor is invalid.');
  321.    end;
  322.  
  323.  procedure checkcompun(strs: strtype);
  324.    begin
  325.      if strs[3][51] = 'N' then
  326.        if (copy(strs[3], 52, 14) <> '            0 ') then
  327.          writeerror(strs[3], 'There were fields present when compu was N.');
  328.    end;
  329.  
  330.  procedure checkempcode(strs: strtype);
  331.    begin
  332.      if copy(strs[2], 64, 4) = 'RET ' then
  333.        if (copy(strs[2], 71, 10) <> '          ') and
  334.           (copy(strs[3], 10, 20) <> '0                   ') and
  335.           (copy(strs[3], 30, 20) <> '0         0  0  0   ') then
  336.           writeerror(strs[3], 'empcodes are present when RET is true.');
  337.    end;
  338.  
  339.  procedure checkcompumon(strs: strtype);
  340.    begin
  341.      if strs[3][61] in ['S','V','E','C','H','I'] then
  342.      else
  343.        writeerror(strs[3], 'compu-mon is invalid.');
  344.    end;
  345.  
  346.  procedure checkcompucdt(strs:strtype);
  347.    begin
  348.      if strs[3][63] in ['1','2','4','6','8'] then
  349.      else
  350.        writeerror(strs[3], 'compu-cdt is invalid.');
  351.    end;
  352.  
  353.  procedure checksttype(strs: strtype);
  354.    var
  355.      a: string;
  356.    begin
  357.      a:=copy(strs[3], 14, 4);
  358.      if (a <> 'BLVD') and (a <> 'LANE') and (a <> 'ST  ') and (a <> 'AVE ')
  359.        and (a <> 'CT  ') and (a <> 'LOOP') and (a <> 'DR  ') and
  360.        (a <> 'CIRC') and (a <> 'RR  ') then
  361.        writeerror(strs[3], 'emp-sttype is invalid.');
  362.      a:=copy(strs[2], 10, 4);
  363.      if (a <> 'BLVD') and (a <> 'LANE') and (a <> 'ST  ') and (a <> 'AVE ')
  364.        and (a <> 'CT  ') and (a <> 'LOOP') and (a <> 'DR  ') and
  365.        (a <> 'CIRC') and (a <> 'RR  ') then
  366.        writeerror(strs[2], 'cnct1-stype is invalid.');
  367.    end;
  368.  
  369.  procedure writeheader;
  370.    begin
  371.      writeln(errfile, 'Error Report -- INDATA.TXT':50);
  372.      writeln(errfile, '--------------------------':50);
  373.      writeln(errfile);
  374.      writeln(errfile, 'First 20 characters','':10, 'Problem');
  375.      writeln(errfile, '-------------------','':10, '-------');
  376.    end;
  377.  
  378.  begin
  379.    ovrinit('PART10.OVR');
  380.    if ovrresult <> 0 then
  381.      begin
  382.        case ovrresult of
  383.          -1: writeln('Overlay manager error.');
  384.          -2: writeln('Overlay file not found.');
  385.          -3: writeln('Not enough memory for overlay buffer.');
  386.          -4: writeln('Overlay I/O error.');
  387.        end;
  388.        halt(1);
  389.      end;
  390.    ovrinitems;
  391.    case ovrresult of
  392.       0: writeln('Overlay loaded to EMS memory!');
  393.      -5: writeln('No EMS driver found! Loading to conventional memory!');
  394.      -6: writeln('Not enough EMS memory to load! Loading to conventional',
  395.                  ' memory!');
  396.    end;
  397.    assign(infile, 'INDATA.TXT');
  398.    reset(infile);
  399.    assign(errfile, 'ERRORS.LOG');
  400.    rewrite(errfile);
  401.    assign(outfile, 'COMPHVN.DAT');
  402.    rewrite(outfile, 1);
  403.    writeheader;
  404.    while not eof(infile) do
  405.      begin
  406.        errwritten := false;
  407.        for i := 1 to 3 do
  408.          readln(infile, strs[i]);
  409.        if checkstatus(strs) then
  410.          begin
  411.            checkdatacodes(strs);
  412.            checknumeric(strs);
  413.            checkprefix(strs);
  414.            checkacct(strs);
  415.            checkyn(strs);
  416.            checkempcode(strs);
  417.            checkcompun(strs);
  418.            checkcompumon(strs);
  419.            checkcompucdt(strs);
  420.            checksttype(strs);
  421.          end;
  422.        if errwritten = false then
  423.          writerecord(outfile, strs);
  424.      end;
  425.    close(infile);
  426.    close(errfile);
  427.    close(outfile);
  428.  end.
  429.  
  430. Basically, the plan for this tutorial is to go through all standard data
  431. types and show how TP stores them in memory, so we will be able to interpret
  432. a binary data file we may create.  Then, using the information we presented
  433. on how things are stored, we will get a spec sheet on a common format, and
  434. interpret the data, so we may be able to obtain data from that file through
  435. a Pascal program.
  436.  
  437. Byte
  438. ====
  439. The basic idea of a byte has been covered in part 7 of the tutorial.
  440. Please refer back to there for a refresher on the concept of what a byte is.
  441.  
  442. Basically, a byte can be used to substitute or represent a char type,
  443. or a number type....The number types, stored as a byte, have a limit of
  444. 0..255 as an unsigned number (byte), and -128..127 with a signed number
  445. (shortint).  The terms in the parentheses are what we would define the
  446. byte to be to get the specified range.  I will explain later in this
  447. tutorial the difference between a "signed" and "unsigned" number, actually
  448. signed and unsigned data space for numbers.
  449.  
  450. The number for an unsigned number is formed, much like we were doing the
  451. binary computations in part 7.
  452.  
  453. Numbers as Integers
  454. ===================
  455. There are several types of numbers we can define...
  456.  
  457. Byte, and ShortInt: as described above in the byte description...
  458.  
  459. Integer types are either as signed (range: -32768..32767) or unsigned
  460. (0..65535) words.  A word is a unit of 2 bytes in TP.  Basically, in
  461. a unsigned integer, the number is calculated from right to left in binary
  462. much like a byte.  Since it is easier, for us to show binary storage
  463. repsentation as hexadecimal units of bytes, we will use hexadecimal values
  464. for an example.
  465.  
  466. An integer type is written to disk, to test something.  The sequence of
  467. two bytes is:  3F 4D  .  What is the number in base 10 form?
  468.  
  469. If we remember from our hex notation, and the way things work:
  470.  
  471.    3 * 16^3 + 15 * 16^2 + 4 * 16^1 + 13 * 16^0 = 16205
  472.  
  473. An integer is ALWAYS two bytes, whether or not the number may technically
  474. fill two bytes in this manner described before.  For example, 13 in base
  475. 10 is stored in memory and on disk by TP in integer form as 00 0D .  We
  476. could equally store this number as a byte if we knew that this variable
  477. location would never be requested to hold a number that is greater than
  478. 255.  For example, if we knew we were going to hold days of the month
  479. in memory, we would never have a number greater than 31, so a byte type
  480. for this number would be appropriate.
  481.  
  482. A longint type is always a signed number, but we do need to know, that
  483. it is what is called a double word, or two words put together.  Therefore,
  484. we know that a longint type is 4 bytes, and has a maximum limit of 2bil.
  485. A longint is ALWAYS 4 bytes, whether or not the number may fill 4 bytes.
  486. 13 in base 10 would be stored in a longint as 00 00 00 0D .
  487.  
  488. Char
  489. ====
  490. A character is stored in memory as a byte, with the byte value
  491. representing the corresponding character as it refers to the
  492. ASCII chart.
  493.  
  494. byte representation 67 as a char is C.
  495.  
  496. Signed vs. Unsigned Integer Numbers
  497. ===================================
  498. We have talked before in this part about signed and unsigned numbers.  In
  499. part 7, essentially, we have covered unsigned numbers, when we were
  500. describing binary logic.  Let's describe what a signed number is.
  501.  
  502. A signed number is represented by either using a base system of 0..1, or
  503. 1..0 in binary.  In a signed number, the leftmost bit is either 0 for a
  504. positive number, and 1 for a negative number.  For positive numbers, the
  505. remaining bits are considered using a 0..1 scheme as we did in part 7
  506. with the binary computations.  For a negative number, we count starting
  507. from 1 and go down to 0.
  508.  
  509. Let's observe what that difference is, by demonstrating how 3 and -3 would
  510. be shown in a shortint (signed) type in binary.
  511.  
  512. Let's start with 3 in binary as a signed number. That is a positive number
  513. so the leftmost bit will be 0.  Then we will use a 0..1 counting system
  514. to finish out the number using standard binary notation.  So, 3 will be
  515. represented in a shortint value as:
  516.  
  517.                              0000     0010 
  518.  
  519. just like it was an unsigned number.  Now, lets observe what -3 would look
  520. like.  That is a negative number, so the leftmost bit would be a 1.  Since
  521. we use a 1..0 system, we would start with 1's in everything.  We know 3 is
  522. 10 in binary, so we use a reverse system and come up with:
  523.  
  524.                              1111     1101
  525.  
  526. To illustrate further, in binary counting (to a computer) -1..-5 would be
  527. (represented in hex) as $FF,$FE,$FD,$FC,$FB; as opposed to $01,$02,$03,
  528. $04,$05 for 1..5 .  In a signed system, negative number counting starts at
  529. -1, which explains why the equal range of a shortint is -128..127, and
  530. there are equally 128 items (positive and negative).
  531.  
  532. As practice, look at the integers.dat now, in a hex viewer, and see exactly
  533. how the numbers are stored.  They are two bytes in length, and should count
  534. from one to ten, as we did in the program.  It should look like this in
  535. your hex viewer....
  536.  
  537. 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A
  538.  
  539. I recommend very heavily that you get a hex viewer to be able to help out.
  540.  
  541. Boolean
  542. =======
  543. 0 = False; 1 = True
  544.  
  545. Common Pascal Strings
  546. =====================
  547. Now we will start to discuss the format of grouped data.  The first format
  548. we will discuss will be the common pascal string.  The format of a pascal
  549. string formatted group of characters is the following.
  550.  
  551. When we define a string without a subscript, we are defining a maximum of
  552. 255 characters. When we define for example, a string[20], we are defining
  553. a max of 20 characters.  In reality, we are defining the number of char-
  554. acters + 1.  A string has a first byte (0th byte) that represents the
  555. actual character length of the data stored in the string, as an unsigned
  556. byte. (Actually, the length function reads this byte when you call it,
  557. but it's also possible to read and refer to the byte, and even SET it.)
  558.  
  559. Refer back to part 8 where I said you could individually set each part
  560. of the string.  It's possible to actually build a string by setting the
  561. length byte, then setting the rest of the string as characters.  Say,
  562. to set the dynamic length of a string to 4, we can do this, as in this
  563. example, which will only write "Hello" instead of "Hello world!".  We
  564. are setting the 0'th part of the string as a character, which we need
  565. to do:
  566.  
  567. program test; 
  568.   var
  569.     p: string;
  570.   begin
  571.     p := 'Hello world!';
  572.     p[0] := #5;
  573.     writeln(p);
  574.   end.
  575.  
  576. Blocked Character Strings
  577. =========================
  578. It's also possible to work with an array of characters as a string, by
  579. referring to the whole array.  The maximum length must be set by the
  580. length of the array, essentially.
  581.  
  582. str: array[1..20] of char;
  583.  
  584. if we read this structure in as a whole, it will have 20 characters in it,
  585. in which we can write back out to the screen by saying WRITE(STR);, or
  586. actually convert to a string by counting through to find the actual length
  587. and then setting the length byte.
  588.  
  589. Null Terminated Strings
  590. =======================
  591. This is a length of characters, which are terminated by a null character,
  592. or #0.  The strings unit is documented to work with these strings, but it's
  593. easier to get away with simply converting it into something we can work
  594. with through Pascal itself, if we have to do much with it.
  595.  
  596. Arrays
  597. ======
  598. The general structure of an array was described in part 6.  It is basically
  599. usable as a grouping of items.
  600.  
  601. Records
  602. =======
  603. A record is stored in memory basically as a grouping of data types.  The
  604. record type below:
  605.  
  606.   datarecord = record
  607.     int: integer;
  608.     character: char;
  609.   end;
  610.  
  611. would be stored in memory like this:
  612.  
  613.  INT CHARACTER
  614.  
  615.  
  616. Now, we have described all of the pertinent items we need to know to be
  617. able to work through a spec sheet on a common format.  Basically, data
  618. for spec sheets are sometimes presented as the record format, in either
  619. Pascal or C format.  We don't need to really do any work for that, but
  620. most of the time, it will be presented in a byte by byte offset format.
  621.  
  622. Let us look at this structure file for an example.  It is of the standard
  623. sound file called a MOD file. (you can find them anywhere, almost)  We
  624. will cover just the header of the file for now...
  625. -------------------------------------------------------------------------
  626.  
  627. Protracker 1.1B Song/Module Format:
  628.  
  629. Offset  Bytes  Description
  630.    0     20    Songname. Remember to put trailing null bytes at the end...
  631.  
  632. Information for sample 1-31:
  633.  
  634. Offset  Bytes  Description
  635.   20     22    Samplename for sample 1. Pad with null bytes.
  636.   42      2    Samplelength for sample 1. Stored as number of words.
  637.                Multiply by two to get real sample length in bytes.
  638.   44      1    Lower four bits are the finetune value, stored as a signed
  639.                four bit number. The upper four bits are not used, and
  640.                should be set to zero.
  641.                Value:  Finetune:
  642.                  0        0
  643.                  1       +1
  644.                  2       +2
  645.                  3       +3
  646.                  4       +4
  647.                  5       +5
  648.                  6       +6
  649.                  7       +7
  650.                  8       -8
  651.                  9       -7
  652.                  A       -6
  653.                  B       -5
  654.                  C       -4
  655.                  D       -3
  656.                  E       -2
  657.                  F       -1
  658.  
  659.   45      1    Volume for sample 1. Range is $00-$40, or 0-64 decimal.
  660.   46      2    Repeat point for sample 1. Stored as number of words offset
  661.                from start of sample. Multiply by two to get offset in bytes.
  662.   48      2    Repeat Length for sample 1. Stored as number of words in
  663.                loop. Multiply by two to get replen in bytes.
  664.  
  665. Information for the next 30 samples starts here. It's just like the info for
  666. sample 1.
  667.  
  668. Offset  Bytes  Description
  669.   50     30    Sample 2...
  670.   80     30    Sample 3...
  671.    .
  672.    .
  673.    .
  674.  890     30    Sample 30...
  675.  920     30    Sample 31...
  676.  
  677. Offset  Bytes  Description
  678.  950      1    Songlength. Range is 1-128.
  679.  951      1    Well... this little byte here is set to 127, so that old
  680.                trackers will search through all patterns when loading.
  681.                Noisetracker uses this byte for restart, but we don't.
  682.  952    128    Song positions 0-127. Each hold a number from 0-63 that
  683.                tells the tracker what pattern to play at that position.
  684. 1080      4    The four letters "M.K." - This is something Mahoney & Kaktus
  685.                inserted when they increased the number of samples from
  686.                15 to 31. If it's not there, the module/song uses 15 samples
  687.                or the text has been removed to make the module harder to
  688.                rip. Startrekker puts "FLT4" or "FLT8" there instead.
  689.  
  690. Source: Lars "ZAP" Hamre/Amiga Freelancers
  691.  
  692. --------------------------------------------------------------------------
  693.  
  694. Building the basic record format
  695. ================================
  696. We will need to essentially, for any standard file, built record format(s)
  697. for the file.  Let us start with this one.
  698.  
  699. It says above that the first 20 bytes would be a song title.  The description
  700. best seems to define it as a null-terminated string, but we know the maximum
  701. limit.  So, lets just call it a blocked character string of length 20.  So
  702. we will use this description for part of our component record:
  703.  
  704. songname: array[1..20] of char;
  705.  
  706. If we read on through the file, it states data for samples 1-31.  They are
  707. varied data, so that infers that we need to build another record format.
  708. But for the position so far in our current record format, we will use
  709. this, since we know that there are a group of 31 samples...we will call
  710. our alternate record for the sample data, samplerec.
  711.  
  712. sampledata: array[1..31] of samplerec;
  713.  
  714. Alternate Sample Record
  715. =======================
  716. 22 bytes are defined for a samplename.  Same logic for songname.  So this
  717. unit of our sample record will be
  718.  
  719. samplename: array[1..22] of char;
  720.  
  721. The next part is a sample length defined by 2 bytes.  So, we could either
  722. call this a word, or an integer:
  723.  
  724. samplelength: integer;
  725.  
  726. One byte for a finetune value.  Logical definition:
  727.  
  728. finetune: byte;
  729.  
  730. Volume is defined as one byte.  So...
  731.  
  732. volume: byte;
  733.  
  734. Repeat point and Repeat length are both defined as words above so...
  735.  
  736. repeatpoint: integer;
  737. repeatlength: integer;
  738.  
  739. It is indicated above that we are done with the samples.  Therefore, our
  740. final record format for the samples would be:
  741.  
  742. samplerec = record
  743.   samplename: array[1..22] of char;
  744.   samplelength: integer;
  745.   finetune: byte;
  746.   volume: byte;
  747.   repeatpoint: integer;
  748.   repeatlength: integer;
  749. end;
  750.  
  751. Finishing the Record Format
  752. ===========================
  753. Continuing on...
  754.  
  755. songlength is a byte.  So
  756.  
  757. songlength: byte;
  758.  
  759. A byte is defined next that seems like a filler byte.  So...
  760.  
  761. filler: byte;
  762.  
  763. The next set of 128 bytes are defined as "song positions 0-127".  Each byte
  764. is also defined to hold a number, so lets keep to that:
  765.  
  766. positions: array[0..127] of byte;
  767.  
  768. The next 4 bytes to finish out our headers is the moduleid.  it's 4 bytes,
  769. text, so...
  770.  
  771. id: array[1..4] of char;
  772.  
  773. Having gone through the header format for this file, we have come up with
  774. a record we can use to read all the header data, that we would need to read
  775. for a mod file.  Therefore, we can use a final type listing in our program
  776. to read a mod file header of:
  777.  
  778. samplerec = record
  779.   samplename: array[1..22] of char;
  780.   samplelength: integer;
  781.   finetune: byte;
  782.   volume: byte;
  783.   repeatpoint: integer;
  784.   repeatlength: integer;
  785. end;
  786.  
  787. modrecord = record
  788.   songname: array[1..20] of char;
  789.   sampledata: array[1..31] of samplerec;
  790.   songlength: byte;
  791.   filler: byte;
  792.   positions: array[0..127] of byte;
  793.   id: array[1..4] of char;
  794. end;
  795.  
  796. We can use this format to gain any information we know about a file.  To
  797. test this format, we need to write a program that pulls the information
  798. out of a standard file, that we know, and check to see if they're identical.
  799. For MODs, we would need to get a player, and load up the player, in order
  800. to get data we are familiar with from the spec sheet, such as the id being
  801. "M.K.".
  802.  
  803. All the data we need to know to extract data out, and get formatted data
  804. from standards files, have been presented.
  805.  
  806. Practice Programming Problem #11
  807. ================================
  808. In keeping with the topic of this tutorial, I am asking you to write a
  809. program entirely in Pascal that will take a ZIP file name from the command-
  810. line and then print a list of all the files within that zip file.  With
  811. each filename, list the compressed size, uncompressed size, date, time,
  812. and compression method, one per file.  At the end, list the total number
  813. of files, total compressed and uncompressed size, effective compression
  814. ratio, and write the comment. Then output this list to a file taken on
  815. the command-line.  For example, a command-line will always be:
  816.  
  817. ZIPLIST FILENAME.ZIP OUTPUT.TXT
  818.  
  819. Be sure to design this program with any error checks you may need to perform.
  820. Don't forget to devise a check to be sure you are dealing with a ZIP file
  821. on the first parameter.  Here, we have gone to look at some references,
  822. and have found the following out of a specs list:
  823.  
  824. --------A-ZIP-------------------------------
  825. The ZIP archives are created by the PkZIP/PkUnZIP combo produced
  826. by the PkWare company. The PkZIP programs have with LHArc and ARJ
  827. the best compression.
  828. The directory information is stored at the end of the archive, each local
  829. file in the archive begins with the following header; This header can be used
  830. to identify a ZIP file as such :
  831. OFFSET              Count TYPE   Description
  832. 0000h                   4 char   ID='PK',03,04
  833. 0004h                   1 word   Version needed to extract archive
  834. 0006h                   1 word   General purpose bit field (bit mapped)
  835.                                       0 - file is encrypted
  836.                                       1 - 8K/4K sliding dictionary used
  837.                                       2 - 3/2 Shannon-Fano trees were used
  838.                                     3-4 - unused
  839.                                    5-15 - used internally by ZIP
  840.                                  Note:  Bits 1 and 2 are undefined if the
  841.                                         compression method is other than
  842.                                         type 6 (Imploding).
  843. 0008h                   1 word   Compression method (see table 0010)
  844. 000Ah                   1 dword  Original DOS file date/time (see table 0009)
  845. 000Eh                   1 dword  32-bit CRC of file (inverse??)
  846. 0012h                   1 dword  Compressed file size
  847. 0016h                   1 dword  Uncompressed file size
  848. 001Ah                   1 word   Length of filename
  849.                                  ="LEN"
  850. 001Ch                   1 word   Length of extra field
  851.                                  ="XLN"
  852. 001Eh               "LEN" char   path/filename
  853. 001Eh               "XLN" char   extra field
  854. +"LEN"
  855. After all the files, there comes the central directory structure.
  856.  
  857. (Table 0009)
  858. Format of the MS-DOS time stamp (32-bit)
  859. The MS-DOS time stamp is limited to an even count of seconds, since the
  860. count for seconds is only 5 bits wide.
  861.  
  862.   31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
  863.  |<---- year-1980 --->|<- month ->|<--- day ---->|
  864.  
  865.   15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
  866.  |<--- hour --->|<---- minute --->|<- second/2 ->|
  867.  
  868. (Table 0010)
  869. PkZip compression types
  870. 0 - Stored / No compression
  871. 1 - Shrunk / LZW, 8K buffer, 9-13 bits with partial clearing
  872. 2 - Reduced-1 / Probalistic compression, lower 7 bits
  873. 3 - Reduced-2 / Probalistic compression, lower 6 bits
  874. 4 - Reduced-3 / Probalistic compression, lower 5 bits
  875. 5 - Reduced-4 / Probalistic compression, lower 4 bits
  876. 6 - Imploded / 2/3 Shanno-Fano trees, 4K/8K sliding dictionary
  877. [7..9 also exist] => note added by Glenn Grotzinger from Phil Katz's
  878. description
  879.  
  880. --- Central directory structure
  881. The CDS is at the end of the archive and contains additional information
  882. about the files stored within the archive.
  883. OFFSET              Count TYPE   Description
  884. 0000h                   4 char   ID='PK',01,02
  885. 0004h                   1 byte   Version made by
  886. 0005h                   1 byte   Host OS (see table 0011)
  887. 0006h                   1 byte   Minimum version needed to extract
  888. 0007h                   1 byte   Target OS
  889.                                  see above "Host OS"
  890. 0008h                   1 word   General purpose bit flag
  891.                                  see above "General purpose bit flag"
  892. 000Ah                   1 word   Compression method
  893.                                  see above "Compression method"
  894. 000Ch                   1 dword  DOS date / time of file (see table 0009)
  895. 0010h                   1 dword  32-bit CRC of file 
  896. 0014h                   1 dword  Compressed size of file
  897. 0018h                   1 dword  Uncompressed size of file
  898. 001Ch                   1 word   Length of filename
  899.                                  ="LEN"
  900. 001Eh                   1 word   Length of extra field
  901.                                  ="XLN"
  902. 0020h                   1 word   Length of file comment
  903.                                  ="CMT"
  904. 0022h                   1 word   Disk number ??
  905. 0024h                   1 word   Internal file attributes (bit mapped)
  906.                                     0 - file is apparently an ASCII/binary file
  907.                                  1-15 - unused
  908. 0026h                   1 dword  External file attributes (OS dependent)
  909. 002Ah                   1 dword  Relative offset of local header from the
  910.                                  start of the first disk this file appears on
  911. 002Eh               "LEN" char   Filename / path; should not contain a drive
  912.                                  or device letter, all slashes should be forward
  913.                                  slashes '/'.
  914. 002Eh+              "XLN" char   Extra field
  915. +"LEN"
  916. 002Eh               "CMT" char   File comment
  917. +"LEN"
  918. +"XLN"
  919.  
  920. (Table 0011)
  921. PkZip Host OS table
  922. 0 - MS-DOS and OS/2 (FAT)
  923. 1 - Amiga
  924. 2 - VMS
  925. 3 - *nix
  926. 4 - VM/CMS
  927. 5 - Atari ST
  928. 6 - OS/2 1.2 extended file sys
  929. 7 - Macintosh
  930. 8-255 - unused
  931.  
  932. --- End of central directory structure
  933. The End of Central Directory Structure header has following format :
  934. OFFSET              Count TYPE   Description
  935. 0000h                   4 char   ID='PK',05,06
  936. 0004h                   1 word   Number of this disk
  937. 0006h                   1 word   Number of disk with start of central directory
  938. 0008h                   1 word   Total number of file/path entries on this disk
  939. 000Ah                   1 word   Total number of entries in central dir
  940. 000Ch                   1 dword  Size of central directory
  941. 0010h                   1 dword  Offset of start of central directory relative
  942.                                  to starting disk number
  943. 0014h                   1 word   Archive comment length
  944.                                  ="CML"
  945. 0016h               "CML" char   Zip file comment
  946.  
  947. EXTENSION:ZIP
  948. OCCURENCES:PC,Amiga,ST
  949. PROGRAMS:PkZIP,WinZIP
  950. REFERENCE:Technote.APP
  951.  
  952. Source: FILEFMTS (c) 1994,1995 Max Maischein
  953.  
  954.  
  955. Sample output for this program
  956. ==============================
  957.  
  958.                        Files contained in FILENAME.ZIP
  959.  
  960. NAME         COMP-SIZE     DATE     TIME     UNCOMP-SIZE  COMP-METHOD       
  961. ---------------------------------------------------------------------
  962. FOOBAR10.TXT       732  10-01-1993  02:30          1021       7
  963.  FOBAR11.ZIP     11021  12-01-1995  22:03         23923       6
  964. ...
  965. ---------------------------------------------------------------------
  966.                1520032                          4023232
  967.  
  968. 15 files; Effective compression ratio: 65.3%
  969.  
  970. < write the comment here, or write "No ZIP comment." if there is no
  971. ZIP comment >
  972.  
  973.  
  974. Notes:
  975. 1) Note how I have the sample output.  It needs to be EXACTLY as I have
  976. listed it.
  977. 2) The orders and the methods you need to use to read files should be
  978. evident from the specs file.  They will be random reads...
  979. 3) With the random reads, it is about impossible to tell what you got
  980. read unless you check first...Notice a good way to check out of the
  981. first field of each record...
  982. 4) The "MS-DOS Time Format" can be done really easily.  Just think DOS
  983. unit.
  984. 5) Do not read anything unless you HAVE to....
  985. 6) Since this is a program that happens to use another program's data,
  986. more than likely, the data are correct, so there is no need to check
  987. any of the data beyond determining whether it's an actual ZIP file.
  988.  
  989. With this data listed, you should be able to do anything related to listing,
  990. stripping comments, showing comments, and so on and so forth.
  991.  
  992. Next Time
  993. =========
  994. We will cover the use of stacks and queues.  Send any comments,
  995. encouragements, problems, etc, etc. to ggrotz@2sprint.net.  Haven't
  996. seen anything of that type, ya know....
  997.  
  998.